package com.corx.network.core.manager;

import com.corx.network.config.properties.DeviceStateConfig;
import com.corx.network.core.handler.DeviceStateHandler;
import com.corx.network.core.type.DeviceStateEnum;
import com.corx.network.core.type.NetworkTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * @author gx
 * @version 1.0.0
 */
@Slf4j
public class DeviceStateManager implements DisposableBean {

    private final Map<String, DeviceState> deviceStates = new ConcurrentHashMap<>();
    private final List<DeviceStateHandler> stateHandlers = new CopyOnWriteArrayList<>();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final DeviceStateConfig config;

    public DeviceStateManager() {
        this(new DeviceStateConfig());
    }

    public DeviceStateManager(DeviceStateConfig config) {
        this.config = Objects.requireNonNull(config, "DeviceStateConfig不能为null");
        scheduler.scheduleAtFixedRate(this::checkTimeout, 10, 5, TimeUnit.SECONDS);
    }

    public void register(DeviceStateHandler handler) {
        stateHandlers.add(Objects.requireNonNull(handler, "DeviceStateHandler不能为null"));
    }

    public void remove(DeviceStateHandler handler) {
        stateHandlers.remove(Objects.requireNonNull(handler, "DeviceStateHandler不能为null"));
    }


    /**
     * 设备上报数据时调用，刷新状态和最后活跃时间
     */
    public void reportAlive(String deviceId, NetworkTypeEnum type) {
        long now = System.currentTimeMillis();
        long timeout = config.getTypeTimeouts().getOrDefault(type, config.getDefaultTimeout());

        deviceStates.compute(deviceId, (k, oldState) -> {
            if (oldState == null) {
                notifyStateChange(k, DeviceStateEnum.ONLINE);
                return new DeviceState(type, DeviceStateEnum.ONLINE, now, timeout);
            } else {
                if (oldState.getStateEnum() != DeviceStateEnum.ONLINE) {
                    notifyStateChange(k, DeviceStateEnum.ONLINE);
                }
                oldState.setStateEnum(DeviceStateEnum.ONLINE);
                oldState.setLastSeen(now);
                oldState.setTimeout(timeout);
                return oldState;
            }
        });
    }


    private void checkTimeout() {
        long now = System.currentTimeMillis();
        for (Iterator<Map.Entry<String, DeviceState>> it = deviceStates.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<String, DeviceState> entry = it.next();
            DeviceState state = entry.getValue();
            if (now - state.getLastSeen() > state.getTimeout()) {
                notifyStateChange(entry.getKey(), DeviceStateEnum.OFFLINE);
                it.remove();
                log.info("设备超时下线并清除: key={}", entry.getKey());
            }
        }
    }

    private void notifyStateChange(String deviceId, DeviceStateEnum state) {
        for (DeviceStateHandler handler : stateHandlers) {
            try {
                handler.onChange(deviceId, state);
            } catch (Exception e) {
                log.error("通知设备状态变化异常: deviceId={}, state={}", deviceId, state, e);
            }
        }
    }

    private void allOffline() {
        for (String key : deviceStates.keySet()) {
            notifyStateChange(key, DeviceStateEnum.OFFLINE);
        }
    }

    @Override
    public void destroy() {
        log.info("DeviceStateManager销毁中，全部设备置为offline...");
        scheduler.shutdownNow();
        allOffline();
        deviceStates.clear();
        stateHandlers.clear();
    }

    static class DeviceState {
        private NetworkTypeEnum typeEnum;
        private DeviceStateEnum stateEnum;
        private long lastSeen;
        private long timeout;

        public DeviceState(NetworkTypeEnum typeEnum, DeviceStateEnum stateEnum, long lastSeen, long timeout) {
            this.typeEnum = typeEnum;
            this.stateEnum = stateEnum;
            this.lastSeen = lastSeen;
            this.timeout = timeout;
        }

        public NetworkTypeEnum getTypeEnum() {
            return typeEnum;
        }

        public void setTypeEnum(NetworkTypeEnum typeEnum) {
            this.typeEnum = typeEnum;
        }

        public DeviceStateEnum getStateEnum() {
            return stateEnum;
        }

        public void setStateEnum(DeviceStateEnum stateEnum) {
            this.stateEnum = stateEnum;
        }

        public long getLastSeen() {
            return lastSeen;
        }

        public long getTimeout() {
            return timeout;
        }

        public void setLastSeen(long lastSeen) {
            this.lastSeen = lastSeen;
        }

        public void setTimeout(long timeout) {
            this.timeout = timeout;
        }
    }
}
